﻿using AbpDemo.AdminRoles;
using AbpDemo.Admins.Dto;
using AbpDemo.Filters;
using AbpDemo.Menus;
using AbpDemo.RoleMenus;
using AbpDemo.Roles;
using AbpDemo.Roles.Dto;
using AbpDemo.Utility;
using CSRedis;
using IdentityModel;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
using QueryDto = AbpDemo.Admins.Dto.QueryDto;

namespace AbpDemo.Admins
{
    [ApiExplorerSettings(GroupName = "v1")]    
    public class AdminService : ApplicationService, IAdminService
    {
        private readonly IRepository<Admin> repository;
        private readonly IConfiguration configuration;
        private readonly IAdminRepository adminRepository;
        private readonly IHttpContextAccessor httpContext;
        private readonly IRepository<Role> repositoryRole;
        private readonly IRepository<Menu> repositoryMenu;
        private readonly IRepository<AdminRole> repositoryAdminrole;
        private readonly IRepository<RoleMenu> repositoryRolemenu;
        private readonly CSRedisClient redisClient;

        public AdminService(
            IRepository<Admin> repository,
            IConfiguration configuration,
            IAdminRepository adminRepository,
            IHttpContextAccessor httpContext,
            IRepository<Role> repositoryRole,
            IRepository<Menu> repositoryMenu,
            IRepository<AdminRole> repositoryAdminrole,
            IRepository<RoleMenu> repositoryRolemenu,
            CSRedisClient redisClient
            )
        {
            this.repository = repository;
            this.configuration = configuration;
            this.adminRepository = adminRepository;
            this.httpContext = httpContext;
            this.repositoryRole = repositoryRole;
            this.repositoryMenu = repositoryMenu;
            this.repositoryAdminrole = repositoryAdminrole;
            this.repositoryRolemenu = repositoryRolemenu;
            this.redisClient = redisClient;
        }
        
        /// <summary>
        /// 创建用户
        /// </summary>
        /// <param name="createDto"></param>
        /// <returns></returns>
        [HttpPost("/Admin/Create")]
        [CustomerAuthorizationFilter("system:user:create")]
        public async Task<RegisterResultDto> CreateAsync(CreateUpdateAdminDto createDto)
        {
            try
            {
                var admin = await repository.FirstOrDefaultAsync(m => m.UserName == createDto.UserName);
                if (admin != null)
                {
                    return new RegisterResultDto { Code = RegisterState.UserNameExists, Message = RegisterState.UserNameExists.EnumToDescription() };
                }
                createDto.Password = createDto.Password.Md5();
                var entity = ObjectMapper.Map<CreateUpdateAdminDto, Admin>(createDto);
                entity.IsLock = false;
                await repository.InsertAsync(entity);

                //插入角色数据
                var roles = createDto.Role.Select(m => new AdminRole
                {
                    AdminId = entity.Id,
                    RoleId = m
                });

                await repositoryAdminrole.InsertManyAsync(roles);

                return new RegisterResultDto { Code = RegisterState.Success, Message = RegisterState.Success.EnumToDescription() };
            }
            catch (Exception e)
            {
                return new RegisterResultDto { Code = RegisterState.Failed, Message = e.Message };
            }
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="Id"></param>
        /// <param name="updateDto"></param>
        /// <returns></returns>
        [HttpPost("/Admin/Update")]
        [UnitOfWork]
        [CustomerAuthorizationFilter("system:user:edit")]
        public async Task<RegisterResultDto> UpdateAsync(Guid Id, CreateUpdateAdminDto updateDto)
        {
            try
            {
                var exists = await repository.FirstOrDefaultAsync(m => m.UserName == updateDto.UserName && m.Id != Id);
                if (exists != null)
                {
                    return new RegisterResultDto { Code = RegisterState.UserNameExists, Message = RegisterState.UserNameExists.EnumToDescription() };
                }

                var admin = await repository.FirstAsync(m=>m.Id == Id);



                if(!string.IsNullOrEmpty(updateDto.Password))
                { 
                    updateDto.Password = updateDto.Password.Md5();
                }
                else
                {
                    updateDto.Password = admin.Password;
                }
                var entity = ObjectMapper.Map(updateDto, admin);
                await repository.UpdateAsync(entity);

                //删除之前的角色数据
                await repositoryAdminrole.DeleteAsync(m => m.AdminId == Id);
                //重新插入角色数据
                var roles = updateDto.Role.Select(m => new AdminRole
                {
                    AdminId = entity.Id,
                    RoleId = m
                });

                await repositoryAdminrole.InsertManyAsync(roles);

                return new RegisterResultDto { Code = RegisterState.Success, Message = RegisterState.Success.EnumToDescription() };
            }
            catch (Exception e)
            {
                return new RegisterResultDto { Code = RegisterState.Failed, Message = e.Message };
            }
        }

        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="adminDto"></param>
        /// <returns></returns>
        [HttpPost("/Admin/Login")]
        [AllowAnonymous]
        public async Task<LoginResultDto<PermissionDto>> LoginAsync(LoginDto adminDto)
        {
            try
            {
                var admin = await repository.FirstOrDefaultAsync(m => m.UserName == adminDto.UserName);
                if (admin == null)
                {
                    return new LoginResultDto<PermissionDto> { Code = LoginState.NotFoundUserName, Message = LoginState.NotFoundUserName.EnumToDescription() };
                }
                else
                {
                    //如果达到解锁条件，则更新锁定状态
                    if (admin.IsLock && DateTime.Now.Subtract((DateTime)admin.LockTime).TotalSeconds > 30)
                    {
                        admin.IsLock = false;
                        admin.LockTime = null;
                        admin.ErrorLoginNum = 0;
                        await repository.UpdateAsync(admin);
                    }

                    //锁定时间小于30分钟并且在锁定状态
                    if (admin.IsLock && DateTime.Now.Subtract((DateTime)admin.LockTime).TotalSeconds < 30)
                    {
                        return new LoginResultDto<PermissionDto> { Code = LoginState.Locked, Message = LoginState.Locked.EnumToDescription() };
                    }
                    else
                    {
                        if (admin.Password != adminDto.Password.Md5())
                        {
                            admin.ErrorLoginNum += 1;

                            if (admin.ErrorLoginNum >= 5)
                            {
                                admin.IsLock = true;
                                admin.LockTime = DateTime.Now;

                                //锁定用户
                                await repository.UpdateAsync(admin);



                                return new LoginResultDto<PermissionDto> { Code = LoginState.Locked, Message = LoginState.Locked.EnumToDescription() };
                            }

                            return new LoginResultDto<PermissionDto> { Code = LoginState.PasswordWrong, Message = LoginState.PasswordWrong.EnumToDescription() };
                        }
                        else
                        {
                            //更新末次登录IP和末次登录时间
                            admin.LastLoginIp = httpContext.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
                            admin.LastLoginTime = DateTime.Now;
                            admin.ErrorLoginNum = 0;
                            await repository.UpdateAsync(admin);

                            //调用生成Token方法
                            var authInfo = await GenerateToken(admin);

                            return new LoginResultDto<PermissionDto> { Code = LoginState.Success, Token = authInfo.Item1, Data = authInfo.Item2 };
                        }
                    }
                }
            }
            catch (Exception e)
            {
                return new LoginResultDto<PermissionDto> { Code = LoginState.Failed, Message = e.Message };
            }
        }

        /// <summary>
        /// 获取用户信息
        /// </summary>
        /// <param name="Token"></param>
        /// <returns></returns>
        [HttpGet("/Admin/Info")]
        [Authorize]
        public async Task<ResultDto<AdminInfo>> Info(string Token)
        {
            var claims = httpContext.HttpContext.User.Claims;

            var currentUser = (
                               from a in await repository.GetQueryableAsync()
                               join b in await repositoryAdminrole.GetQueryableAsync()
                               on a.Id equals b.AdminId
                               join c in await repositoryRole.GetQueryableAsync()
                               on b.RoleId equals c.Id
                               join d in await repositoryRolemenu.GetQueryableAsync()
                               on b.RoleId equals d.RoleId
                               join e in await repositoryMenu.GetQueryableAsync()
                               on d.MenuId equals e.Id
                               where a.UserName == httpContext.HttpContext.User.Identity.Name
                               select new { c.RoleCode, e.Permission }
                               ).ToList();

            //当前用户角色
            var roleCodeList = string.Join(',', currentUser.Select(a => a.RoleCode).Distinct());

            //当前用户权限
            var permission = currentUser.Select(a => a.Permission).Distinct().ToList();

            //所有权限
            var allPermission = (from a in await repositoryMenu.GetQueryableAsync()
                                 where a.Permission != null
                                 select a.Permission).Distinct().ToList();

            return new ResultDto<AdminInfo>
            {
                Code = ResultState.Success,
                Data = new AdminInfo
                {
                    avatar = claims.FirstOrDefault(m => m.Type == nameof(Admin.Avatar)).Value,
                    introduction = claims.FirstOrDefault(m => m.Type == nameof(Admin.Introduction)).Value,
                    name = httpContext.HttpContext.User.Identity.Name,
                    roles = claims.FirstOrDefault(m => m.Type == ClaimTypes.Role).Value.Split(','),
                    Permission = permission,
                    AllPermission = allPermission
                }
            };
        }

        /// <summary>
        /// 退出登录
        /// </summary>
        /// <returns></returns>
        [HttpPost("/Admin/Logout")]
        [Authorize]
        public async Task<ResultDto> Logout()
        {
            await redisClient.DelAsync("Abp:Token");
            return new ResultDto { Code = ResultState.Success };
        }

        /// <summary>
        /// 初始化数据
        /// </summary>
        /// <returns></returns>
        [HttpGet("/Admin/InitData")]
        [Authorize]
        public async Task InitDataAsync()
        {
            //初始化AdminRole
            var admin = await repository.GetListAsync();
            var role = await repositoryRole.GetListAsync();
            var menu = await repositoryMenu.GetListAsync();

            List<AdminRole> adminRoles = new List<AdminRole>();
            admin.ForEach(admin =>
            {
                role.ForEach(role =>
                {
                    adminRoles.Add(new AdminRole
                    {
                        AdminId = admin.Id,
                        RoleId = role.Id
                    });
                });
            });

            await repositoryAdminrole.InsertManyAsync(adminRoles);

            //初始化AdminRole
            List<RoleMenu> roleMenus = new List<RoleMenu>();
            role.ForEach(role =>
            {
                menu.ForEach(menu =>
                {
                    roleMenus.Add(new RoleMenu
                    {
                        MenuId = menu.Id,
                        RoleId = role.Id
                    });
                });
            });

            await repositoryRolemenu.InsertManyAsync(roleMenus);
        }

        /// <summary>
        /// 分页
        /// </summary>
        /// <param name="queryDto"></param>
        /// <returns></returns>
        [HttpGet("/Admin/Page")]
        [CustomerAuthorizationFilter("system:user:page")]
        public async Task<ResultDto<PagedResultDto<AdminDto>>> PageAsync([FromQuery] QueryDto queryDto)
        {
            var list = await repository.GetQueryableAsync();
            list = list.WhereIf(!string.IsNullOrEmpty(queryDto.UserName), m => m.UserName.Contains(queryDto.UserName));
            list = list.WhereIf(!string.IsNullOrEmpty(queryDto.Mobile), m => m.UserName.Contains(queryDto.Mobile));
            list = list.WhereIf(queryDto.StartTime != null, m => m.CreationTime > queryDto.StartTime);
            list = list.WhereIf(queryDto.EndTime != null, m => m.CreationTime < queryDto.EndTime);

            var page = ObjectMapper.Map<List<Admin>, List<AdminDto>>(list.OrderByDescending(m => m.CreationTime).Skip((queryDto.PageIndex - 1) * queryDto.PageSize).Take(queryDto.PageSize).ToList());
            var totalCount = list.Count();

            var roles = (from role in await repositoryRole.GetQueryableAsync()
                         join adminrole in await repositoryAdminrole.GetQueryableAsync()
                         on role.Id equals adminrole.RoleId
                         select new AdminRoleDto
                         {
                             RoleName = role.RoleName,
                             AdminId = adminrole.AdminId,
                             RoleId = role.Id,
                             Id = adminrole.Id
                         }).ToList();

            foreach (var item in page)
            {
                item.AdminRoleDtos = roles.Where(m => m.AdminId == item.Id).ToArray();
            }

            return new ResultDto<PagedResultDto<AdminDto>>
            {
                Code = ResultState.Success,
                Data = new PagedResultDto<AdminDto>
                {
                    Items = page,
                    TotalCount = totalCount
                }
            };
        }

        /// <summary>
        /// 锁定用户
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("/Admin/Lock")]
        [CustomerAuthorizationFilter("system:user:lock")]
        public async Task<ResultDto<AdminDto>> Lock(AdminDto dto)
        {
            try
            {
                var admin = await repository.FirstOrDefaultAsync(m => m.Id == dto.Id);
                if (admin != null)
                {
                    if (!dto.IsLock)
                    {
                        admin.IsLock = !admin.IsLock;
                        admin.LockTime = null;
                        admin.ErrorLoginNum = 0;
                    }
                    else
                    {
                        admin.IsLock = !admin.IsLock;
                        admin.LockTime = DateTime.Now;
                        admin.ErrorLoginNum = 0;
                    }
                }
                var adminDto = ObjectMapper.Map<Admin, AdminDto>(await repository.UpdateAsync(admin));
                return new ResultDto<AdminDto> { Code = ResultState.Success, Data = adminDto };
            }
            catch (Exception e)
            {
                return new ResultDto<AdminDto> { Code = ResultState.Failed, Message = e.Message };
            }
        }

        /// <summary>
        /// 行内编辑更新手机号
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("/Admin/UpdateMobile")]
        [CustomerAuthorizationFilter("system:user:edit")]
        public async Task<ResultDto<bool>> UpdateMobileAsync(AdminDto dto)
        {
            var admin = await repository.FirstOrDefaultAsync(m => m.Id == dto.Id);
            if (admin != null)
            {
                admin.Mobile = dto.Mobile;
            }
            return new ResultDto<bool> { Code = ResultState.Success };
        }

        /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        [HttpGet("/Admin/Get")]
        [CustomerAuthorizationFilter("system:user:edit")]
        public async Task<ResultDto<AdminDto>> GetAsync(Guid id)
        {
            var admin = ObjectMapper.Map < Admin, AdminDto>(await repository.FirstAsync(m => m.Id == id));

            admin.AdminRoleDtos = (from role in await repositoryRole.GetQueryableAsync()
                                   join adminrole in await repositoryAdminrole.GetQueryableAsync()
                                   on role.Id equals adminrole.RoleId
                                   where adminrole.AdminId == admin.Id
                                   select new AdminRoleDto
                                   {
                                       RoleName = role.RoleName,
                                       AdminId = adminrole.AdminId,
                                       RoleId = role.Id,
                                       Id = adminrole.Id
                                   }).ToArray();

            return new ResultDto<AdminDto>
            {
                Code = ResultState.Success,
                Data = admin
            };
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        [HttpGet("/Admin/Delete")]
        [CustomerAuthorizationFilter("system:user:delete")]
        public async Task<ResultDto<AdminDto>> DeleteAsync(Guid id)
        {
            await repository.DeleteAsync(m => m.Id == id);
            //删除中间表数据
            await repositoryAdminrole.DeleteAsync(m=>m.AdminId == id);
            return new ResultDto<AdminDto> {  Code = ResultState.Success };
        }

        [HttpGet("/Admin/RefreshToken")]
        public async Task<ResultDto<string>> RefreshToken(string Token)
        {
            if(Token == "undefined")
            {
                return new ResultDto<string> { Code = ResultState.Failed };
            }
            var Claim =JsonConvert.DeserializeObject<ClaimDto>(Token.Decode(configuration["JwtConfig:Bearer:SecurityKey"]));

            var admin = await repository.FirstAsync(m => m.UserName == Claim.name);

            if(admin != null)
            {
                var authInfo = await GenerateToken(admin);

                return new ResultDto<string> { Code = ResultState.Success, Data = authInfo.Item1 };
            }
            else
            {
                return new ResultDto<string> { Code = ResultState.Failed };
            }
        }

        private async Task<(string, PermissionDto)> GenerateToken(Admin admin)
        {
            var currentUser = (from a in await repository.GetQueryableAsync()
                               join b in await repositoryAdminrole.GetQueryableAsync()
                               on a.Id equals b.AdminId
                               join c in await repositoryRole.GetQueryableAsync()
                               on b.RoleId equals c.Id
                               join d in await repositoryRolemenu.GetQueryableAsync()
                               on b.RoleId equals d.RoleId
                               join e in await repositoryMenu.GetQueryableAsync()
                               on d.MenuId equals e.Id
                               where a.UserName == admin.UserName
                               select new { c.RoleCode, e.Permission }
                                       ).ToList();

            //当前用户角色
            var roleCodeList = string.Join(',', currentUser.Select(a => a.RoleCode).Distinct());

            //当前用户权限
            var permission = currentUser.Select(a => a.Permission).Where(m => !string.IsNullOrEmpty(m)).Distinct().ToList();

            //所有权限
            var allPermission = (from a in await repositoryMenu.GetQueryableAsync()
                                 where a.Permission != null
                                 select a.Permission).Distinct().ToList();

            PermissionDto permissionDto = new PermissionDto
            {
                Permission = permission,
                AllPermission = allPermission
            };

            IList<Claim> claims = new List<Claim> {
                                new Claim(JwtClaimTypes.Id, admin.Id.ToString()),
                                new Claim(JwtClaimTypes.Name, admin.UserName),
                                new Claim(ClaimTypes.Role, roleCodeList),
                                new Claim(nameof(Admin.Avatar),admin.Avatar),
                                new Claim(nameof(Menu.Permission),string.Join(',',permission)),
                                new Claim(nameof(Admin.Introduction),admin.Introduction),
                            };

            //JWT密钥
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtConfig:Bearer:SecurityKey"]));

            //算法
            var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            //过期时间
            DateTime expires = DateTime.UtcNow.AddSeconds(30);

            //Payload负载
            var token = new JwtSecurityToken(
                issuer: configuration["JwtConfig:Bearer:Issuer"],
                audience: configuration["JwtConfig:Bearer:Audience"],
                claims: claims,
                notBefore: DateTime.UtcNow,
                expires: expires,
                signingCredentials: cred
                );

            var handler = new JwtSecurityTokenHandler();

            //生成令牌
            string jwt = handler.WriteToken(token);

            //将Token存入Redis
            await redisClient.SetAsync("Abp:Token", jwt, TimeSpan.FromSeconds(30));

            return (jwt, permissionDto);
        }
    }
}
